nonlin_polynomials.f90 Source File


Source Code

module nonlin_polynomials
    use iso_fortran_env
    use linalg, only : eigen, solve_least_squares
    use nonlin_error_handling
    implicit none
    private
    public :: polynomial
    public :: assignment(=)
    public :: operator(+)
    public :: operator(-)
    public :: operator(*)

! ******************************************************************************
! INTERFACES
! ------------------------------------------------------------------------------
    interface assignment(=)
        !! Defines polynomial assignment.
        module procedure :: poly_equals
        module procedure :: poly_dbl_equals
        module procedure :: poly_equals_array
    end interface

    interface operator(+)
        !! Defines polynomial addition.
        module procedure :: poly_poly_add
    end interface

    interface operator(-)
        !! Defines polynomial subtraction.
        module procedure :: poly_poly_subtract
    end interface

    interface operator(*)
        !! Defines polynomial multiplication
        module procedure :: poly_poly_mult
        module procedure :: poly_dbl_mult
        module procedure :: dbl_poly_mult
    end interface

! ******************************************************************************
! TYPES
! ------------------------------------------------------------------------------
    type polynomial
        !! Defines a polynomial, and associated routines for performing
        !! polynomial operations.
        real(real64), private, allocatable, dimension(:) :: m_coeffs
            !! An array that contains the polynomial coefficients in ascending 
            !! order.
    contains
        generic, public :: initialize => init_poly, init_poly_coeffs
        procedure, public :: order => get_poly_order
        procedure, public :: fit => poly_fit
        procedure, public :: fit_thru_zero => poly_fit_thru_zero
        generic, public :: evaluate => evaluate_real, evaluate_complex
        procedure, public :: companion_mtx => poly_companion_mtx
        procedure, public :: roots => poly_roots
        procedure, public :: get => get_poly_coefficient
        procedure, public :: get_all => get_poly_coefficients
        procedure, public :: set => set_poly_coefficient
        procedure, public :: divide => poly_divide

        procedure, private :: evaluate_real => poly_eval_double
        procedure, private :: evaluate_complex => poly_eval_complex
        procedure, private :: init_poly
        procedure, private :: init_poly_coeffs
    end type

    interface polynomial
        module procedure :: poly_init_1
        module procedure :: poly_init_2
    end interface

contains
! ******************************************************************************
! POLYNOMIAL MEMBERS
! ------------------------------------------------------------------------------
    pure subroutine init_poly(this, order)
        !! Initializes the polynomial instance.
        class(polynomial), intent(inout) :: this
            !! The [[polynomial]] object.
        integer(int32), intent(in) :: order
            !! The order of the polynomial (must be >= 0).

        ! Parameters
        real(real64), parameter :: zero = 0.0d0

        ! Local Variables
        integer(int32) :: n

        ! Initialization
        n = order + 1

        ! Input Check
        if (order < 0) then
            ! ERROR: Negative order is not supported
            error stop 2
        end if

        ! Process
        if (allocated(this%m_coeffs)) deallocate(this%m_coeffs)
        allocate(this%m_coeffs(n))
        this%m_coeffs = zero
    end subroutine

! ------------------------------------------------------------------------------
    pure subroutine init_poly_coeffs(this, c)
        !! Initializes the polynomial instance.
        class(polynomial), intent(inout) :: this
            !! The [[polynomial]] object.
        real(real64), intent(in), dimension(:) :: c
            !! The array of polynomial coefficients. The coefficients are
            !! established as follows: c(1) + c(2) * x + c(3) * x**2 + ...
            !! c(n) * x**n-1.

        ! Local Variables
        integer(int32) :: i, n

        ! Initialization
        n = size(c)

        ! Initialize the polynomial
        call init_poly(this, n - 1)

        ! Populate the polynomial coefficients
        do i = 1, n
            call this%set(i, c(i))
        end do
    end subroutine

! ------------------------------------------------------------------------------
    pure function get_poly_order(this) result(n)
        !! Returns the order of the polynomial object.
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        integer(int32) :: n
            !! The order of the polynomial.  Returns -1 in the event no
            !! polynomial coefficients have been defined.
        if (.not.allocated(this%m_coeffs)) then
            n = -1
        else
            n = size(this%m_coeffs) - 1
        end if
    end function

! ------------------------------------------------------------------------------
    pure subroutine poly_fit(this, x, y, order)
        !! Fits a polynomial of the specified order to the supplied data set.
        class(polynomial), intent(inout) :: this
            !! The [[polynomial]] object.
        real(real64), intent(in), dimension(:) :: x
            !! An N-element array containing the independent variable data
            !! points.  Notice, must be N > order.
        real(real64), intent(inout), dimension(:) :: y
            !! On input, an N-element array containing the dependent variable 
            !! data points.  On output, the contents are overwritten.
        integer(int32), intent(in) :: order
            !! The order of the polynomial (must be >= 1).

        ! Parameters
        real(real64), parameter :: one = 1.0d0

        ! Local Variables
        integer(int32):: j, n, ncols, flag
        real(real64), pointer, dimension(:,:) :: a

        ! Initialization
        n = size(x)
        ncols = order + 1

        ! Input Check
        if (size(y) /= n) then
            ! ERROR: Array size mismatch
            error stop 3
        end if
        if (order >= n .or. order < 1) then
            ! ERROR: Requested order does not make sense
            error stop 4
        end if

        ! Local Memory Allocation
        allocate(a(n,ncols))

        ! Ensure the polynomial object is initialized and sized appropriately
        if (this%order() /= order) then
            call this%initialize(order)
        end if

        ! Populate A
        do j = 1, n
            a(j,1) = one
            a(j,2) = x(j)
        end do
        do j = 3, ncols
            a(:,j) = a(:,j-1) * x
        end do

        ! Solve: A * coeffs = y
        this%m_coeffs = solve_least_squares(a, y)
    end subroutine

! ------------------------------------------------------------------------------
    pure subroutine poly_fit_thru_zero(this, x, y, order)
        !! Fits a polynomial of the specified order that passes through zero
        !! to the supplied data set.
        class(polynomial), intent(inout) :: this
            !! The [[polynomial]] object.
        real(real64), intent(in), dimension(:) :: x
            !! An N-element array containing the independent variable data
            !! points.  Notice, must be N > order.
        real(real64), intent(inout), dimension(:) :: y
            !! On input, an N-element array containing the dependent
            !! variable data points.  On output, the contents are overwritten.
        integer(int32), intent(in) :: order
            !! The order of the polynomial (must be >= 1).

        ! Parameters
        real(real64), parameter :: zero = 0.0d0

        ! Local Variables
        integer(int32):: j, n, ncols, flag
        real(real64), pointer, dimension(:,:) :: a

        ! Initialization
        n = size(x)
        ncols = order

        ! Input Check
        if (size(y) /= n) then
            ! ERROR: Array size mismatch
            error stop 3
        else if (order >= n .or. order < 1) then
            ! ERROR: Requested order does not make sense
            error stop 4
        end if

        ! Local Memory Allocation
        allocate(a(n,ncols))

        ! Ensure the polynomial object is initialized and sized appropriately
        if (this%order() /= order) then
            call this%initialize(order)
        end if

        ! Populate A
        a(:,1) = x
        do j = 2, ncols
            a(:,j) = a(:,j-1) * x
        end do

        ! Solve: A * coeffs = y
        this%m_coeffs(1) = zero
        this%m_coeffs(2:ncols+1) = solve_least_squares(a, y)
    end subroutine

! ------------------------------------------------------------------------------
    pure elemental function poly_eval_double(this, x) result(y)
        !! Evaluates a polynomial at the specified points.
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        real(real64), intent(in) :: x
            !! The value(s) at which to evaluate the polynomial.
        real(real64) :: y
            !! The value(s) of the polynomial at x.

        ! Parameters
        real(real64), parameter :: zero = 0.0d0

        ! Local Variables
        integer(int32) :: j, order, n

        ! Initialization
        order = this%order()
        n = order + 1
        if (order == -1) then
            y = zero
            return
        else if (order == 0) then
            y = this%m_coeffs(1)
            return
        end if

        ! Process
        y =this%m_coeffs(n) * x + this%m_coeffs(order)
        do j = n - 2, 1, -1
            y = y * x + this%m_coeffs(j)
        end do
    end function

! ------------------------------------------------------------------------------
    pure elemental function poly_eval_complex(this, x) result(y)
        !! Evaluates a polynomial at the specified points.
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        complex(real64), intent(in) :: x
            !! The value(s) at which to evaluate the polynomial.
        complex(real64) :: y
            !! The value(s) of the polynomial at x.

        ! Parameters
        complex(real64), parameter :: zero = (0.0d0, 0.0d0)

        ! Local Variables
        integer(int32) :: j, order, n

        ! Initialization
        order = this%order()
        n = order + 1
        if (order == -1) then
            y = zero
            return
        else if (order == 0) then
            y = this%m_coeffs(1)
            return
        end if

        ! Process
        y =this%m_coeffs(n) * x + this%m_coeffs(order)
        do j = n - 2, 1, -1
            y = y * x + this%m_coeffs(j)
        end do
    end function

! ------------------------------------------------------------------------------
    pure function poly_companion_mtx(this) result(c)
        !! Returns the companion matrix for the polynomial.
        !!
        !! See Also
        !!
        !! - <a href="https://en.wikipedia.org/wiki/Companion_matrix"
        !! target="_blank">Wikipedia</a>
        !!
        !! - <a href="http://mathworld.wolfram.com/CompanionMatrix.html"
        !! target="_blank">Wolfram MathWorld</a>
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        real(real64), dimension(this%order(), this%order()) :: c
            !! The companion matrix.

        ! Parameters
        real(real64), parameter :: zero = 0.0d0
        real(real64), parameter :: one = 1.0d0

        ! Local Variables
        integer(int32) :: i, n

        ! Process
        n = this%order()
        if (n == -1) return
        c = zero
        do i = 1, n
            c(i,n) = -this%m_coeffs(i) / this%m_coeffs(n + 1)
            if (i < n) c(i+1,i) = one
        end do
    end function

! ------------------------------------------------------------------------------
    pure function poly_roots(this) result(z)
        !! Computes all the roots of a polynomial by computing the eigenvalues
        !! of the polynomial companion matrix.
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        complex(real64), allocatable, dimension(:) :: z
            !! The roots of the polynomial.

        ! Local Variables
        integer(int32) :: n
        real(real64), allocatable, dimension(:,:) :: c

        ! Initialization
        n = this%order()

        ! Quick Return
        if (n == 0) return

        ! Compute the companion matrix
        c = this%companion_mtx()

        ! Compute the eigenvalues of the companion matrix.  The eigenvalues are
        ! the roots of the polynomial.
        call eigen(c, vals = z)
    end function

! ------------------------------------------------------------------------------
    pure function get_poly_coefficient(this, ind) result(c)
        !! Gets the requested polynomial coefficient by index.  The
        !! coefficient index is established as follows: c(1) + c(2) * x +
        !! c(3) * x**2 + ... c(n) * x**n-1.
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        integer(int32), intent(in) :: ind
            !! The polynomial coefficient index.
        real(real64) :: c
            !! The requested coefficient.

        ! Initialization
        c = 0.0d0

        ! Quick Return
        if (this%order() == -1) error stop NL_INVALID_OPERATION_ERROR

        ! Input Check
        if (ind <= 0 .or. ind > this%order() + 1) then
            ! ERROR: Index out of range
            error stop NL_INDEX_OUT_OF_RANGE_ERROR
        end if

        ! Get the coefficient
        c = this%m_coeffs(ind)
    end function

! ------------------------------------------------------------------------------
    pure function get_poly_coefficients(this) result(c)
        !! Gets an array containing all the coefficients of the polynomial.
        !! The coefficient index is established as follows: c(1) + c(2) * x +
        !! c(3) * x**2 + ... c(n) * x**n-1.
        class(polynomial), intent(in) :: this
            !! The [[polynomial]] object.
        real(real64), dimension(this%order() + 1) :: c
            !! The array of coefficients.

        ! Process
        if (this%order() == -1) return
        c = this%m_coeffs
    end function

! ------------------------------------------------------------------------------
    pure subroutine set_poly_coefficient(this, ind, c)
        !! Sets the requested polynomial coefficient by index.  The
        !! coefficient index is established as follows: c(1) + c(2) * x +
        !! c(3) * x**2 + ... c(n) * x**n-1.
        class(polynomial), intent(inout) :: this
            !! The [[polynomial]] object.
        integer(int32), intent(in) :: ind
            !! The polynomial coefficient index.
        real(real64), intent(in) :: c
            !! The polynomial coefficient.

        ! Quick Return
        if (this%order() == -1) return

        ! Input Check
        if (ind <= 0 .or. ind > this%order() + 1) then
            ! ERROR: Index out of range
            error stop NL_INDEX_OUT_OF_RANGE_ERROR
        end if

        ! Process
        this%m_coeffs(ind) = c
    end subroutine

! ******************************************************************************
! OPERATORS
! ------------------------------------------------------------------------------
    pure subroutine poly_equals(x, y)
        !! Assigns the contents of one polynomial to another.
        class(polynomial), intent(inout) :: x
            !! The assignee.
        class(polynomial), intent(in) :: y
            !! The item to copy.

        ! Local Variables
        integer(int32) :: i, ord

        ! Process
        ord = y%order()
        if (x%order() /= ord) call x%initialize(ord)
        do i = 1, ord + 1
            call x%set(i, y%get(i))
        end do
    end subroutine

! ------------------------------------------------------------------------------
    pure subroutine poly_dbl_equals(x, y)
        !! Assigns a number to each coefficient of the polynomial.
        class(polynomial), intent(inout) :: x
            !! The assignee.
        real(real64), intent(in) :: y
            !! The value to assign.

        ! Local Variables
        integer(int32) :: i, ord

        ! Process
        ord = x%order()
        do i = 1, ord + 1
            call x%set(i, y)
        end do
    end subroutine

! ------------------------------------------------------------------------------
    pure subroutine poly_equals_array(x, y)
        !! Assigns the contents of an array as polynomial coefficients.
        class(polynomial), intent(inout) :: x
            !! The assignee.
        real(real64), intent(in), dimension(:) :: y
            !! The coefficient array.
        call x%initialize(y)
    end subroutine

! ------------------------------------------------------------------------------
    pure function poly_poly_add(x, y) result(z)
        !! Adds two polynomials.
        class(polynomial), intent(in) :: x
            !! The left-hand-side argument.
        class(polynomial), intent(in) :: y
            !! The right-hand-side argument.
        type(polynomial) :: z
            !! The resulting polynomial.

        ! Local Variables
        integer(int32) :: i, max_ord, x_ord, y_ord

        ! Initialization
        x_ord = x%order()
        y_ord = y%order()
        max_ord = max(x_ord, y_ord)
        call z%initialize(max_ord)

        ! Quick Return
        if (x_ord == -1 .and. y_ord == -1) return
        if (x_ord == -1 .and. y_ord /= -1) then
            do i = 1, max_ord + 1
                call z%set(i, y%get(i))
            end do
            return
        else if (x_ord /= -1 .and. y_ord == -1) then
            do i = 1, max_ord + 1
                call z%set(i, x%get(i))
            end do
            return
        end if

        ! Process
        if (x_ord > y_ord) then
            do i = 1, y_ord + 1
                call z%set(i, x%get(i) + y%get(i))
            end do
            do i = y_ord + 2, x_ord
                call z%set(i, x%get(i))
            end do
        else if (x_ord < y_ord) then
            do i = 1, x_ord + 1
                call z%set(i, x%get(i) + y%get(i))
            end do
            do i = x_ord + 2, y_ord + 1
                call z%set(i, y%get(i))
            end do
        else
            do i = 1, max_ord + 1
                call z%set(i, x%get(i) + y%get(i))
            end do
        end if
    end function

! ------------------------------------------------------------------------------
    pure function poly_poly_subtract(x, y) result(z)
        !! Subtracts two polynomials.
        class(polynomial), intent(in) :: x
            !! The left-hand-side argument.
        class(polynomial), intent(in) :: y
            !! The right-hand-side argument.
        type(polynomial) :: z
            !! The resulting polynomial.

        ! Local Variables
        integer(int32) :: i, max_ord, x_ord, y_ord

        ! Initialization
        x_ord = x%order()
        y_ord = y%order()
        max_ord = max(x_ord, y_ord)
        call z%initialize(max_ord)

        ! Quick Return
        if (x_ord == -1 .and. y_ord == -1) return
        if (x_ord == -1 .and. y_ord /= -1) then
            do i = 1, max_ord + 1
                call z%set(i, y%get(i))
            end do
            return
        else if (x_ord /= -1 .and. y_ord == -1) then
            do i = 1, max_ord + 1
                call z%set(i, x%get(i))
            end do
            return
        end if

        ! Process
        if (x_ord > y_ord) then
            do i = 1, y_ord + 1
                call z%set(i, x%get(i) - y%get(i))
            end do
            do i = y_ord + 2, x_ord
                call z%set(i, x%get(i))
            end do
        else if (x_ord < y_ord) then
            do i = 1, x_ord + 1
                call z%set(i, x%get(i) - y%get(i))
            end do
            do i = x_ord + 2, y_ord + 1
                call z%set(i, -y%get(i))
            end do
        else
            do i = 1, max_ord + 1
                call z%set(i, x%get(i) - y%get(i))
            end do
        end if
    end function

! ------------------------------------------------------------------------------
    pure function poly_poly_mult(x, y) result(z)
        !! Multiplies two polynomials.
        class(polynomial), intent(in) :: x
            !! The left-hand-side argument.
        class(polynomial), intent(in) :: y
            !! The right-hand-side argument.
        type(polynomial) :: z
            !! The resulting polynomial.

        ! Local Variables
        integer(int32) :: i, j, m, n
        real(real64) :: val

        ! Initialization
        n = x%order() + 1
        m = y%order() + 1
        call z%initialize(x%order() + y%order()) ! Sets z to all zeros

        ! Process
        do i = 1, n
            do j = 1, m
                val = z%get(i + j - 1) + x%get(i) * y%get(j)
                call z%set(i + j - 1, val)
            end do
        end do
    end function

! ------------------------------------------------------------------------------
    pure function poly_dbl_mult(x, y) result(z)
        !! Multiplies a polynomial by a scalar value.
        class(polynomial), intent(in) :: x
            !! The left-hand-side argument.
        real(real64), intent(in) :: y
            !! The right-hand-side argument.
        type(polynomial) :: z
            !! The resulting polynomial.

        ! Local Variables
        integer(int32) :: i, ord

        ! Process
        ord = x%order()
        call z%initialize(ord)
        do i = 1, ord + 1
            call z%set(i, x%get(i) * y)
        end do
    end function

! ------------------------------------------------------------------------------
    pure function dbl_poly_mult(x, y) result(z)
        !! Multiplies a polynomial by a scalar value.
        real(real64), intent(in) :: x
            !! The left-hand-side argument.
        class(polynomial), intent(in) :: y
            !! The right-hand-side argument.
        type(polynomial) :: z
            !! The resulting polynomial.

        ! Local Variables
        integer(int32) :: i, ord

        ! Process
        ord = y%order()
        call z%initialize(ord)
        do i = 1, ord + 1
            call z%set(i, y%get(i) * x)
        end do
    end function

! ------------------------------------------------------------------------------
    pure subroutine poly_divide(numerator, divisor, quotient, remainder)
        !! Divides one polynomial by another and returns the quotient and
        !! remainder.
        class(polynomial), intent(in) :: numerator
            !! The numerator polynomial.
        class(polynomial), intent(in) :: divisor
            !! The denominator polynomial.
        type(polynomial), intent(out) :: quotient
            !! The quotient polynomial.
        type(polynomial), intent(out) :: remainder
            !! The remainder polynomial.

        ! Parameters
        real(real64), parameter :: zero = 0.0d0

        ! Local Variables
        integer(int32) :: i, j, n, m, q_order, r_order, last_nonzero
        real(real64) :: coeff, lead
        real(real64), allocatable, dimension(:) :: num_coeffs
        real(real64), allocatable, dimension(:) :: den_coeffs
        real(real64), allocatable, dimension(:) :: q_coeffs
        real(real64), allocatable, dimension(:) :: r_coeffs

        ! Initialization

        call quotient%initialize(0)
        call remainder%initialize(0)

        ! Input Check
        if (numerator%order() == -1) error stop 1
        if (divisor%order() == -1) error stop 2

        ! Process
        num_coeffs = numerator%get_all()
        den_coeffs = divisor%get_all()
        lead = den_coeffs(size(den_coeffs))
        if (abs(lead) <= epsilon(lead)) error stop NL_DIVIDE_BY_ZERO_ERROR

        n = size(num_coeffs) - 1
        m = size(den_coeffs) - 1

        if (n < m) then
            call remainder%initialize(n)
            do i = 1, n + 1
                call remainder%set(i, num_coeffs(i))
            end do
            return
        end if

        allocate(q_coeffs(n - m + 1))
        allocate(r_coeffs(n + 1))
        q_coeffs = zero
        r_coeffs = zero
        r_coeffs = num_coeffs

        do i = n - m, 0, -1
            coeff = r_coeffs(i + m + 1) / lead
            q_coeffs(i + 1) = coeff
            do j = 1, m + 1
                r_coeffs(i + j) = r_coeffs(i + j) - coeff * den_coeffs(j)
            end do
        end do

        ! Trim any leading zero coefficients from the quotient.
        last_nonzero = 0
        do i = size(q_coeffs), 1, -1
            if (abs(q_coeffs(i)) > epsilon(q_coeffs(i))) then
                last_nonzero = i
                exit
            end if
        end do
        if (last_nonzero == 0) then
            call quotient%initialize(0)
        else
            q_order = last_nonzero - 1
            call quotient%initialize(q_order)
            do i = 1, last_nonzero
                call quotient%set(i, q_coeffs(i))
            end do
        end if

        ! Trim any leading zero coefficients from the remainder.
        last_nonzero = 0
        do i = size(r_coeffs), 1, -1
            if (abs(r_coeffs(i)) > epsilon(r_coeffs(i))) then
                last_nonzero = i
                exit
            end if
        end do
        if (last_nonzero == 0) then
            call remainder%initialize(0)
        else
            r_order = last_nonzero - 1
            call remainder%initialize(r_order)
            do i = 1, last_nonzero
                call remainder%set(i, r_coeffs(i))
            end do
        end if
    end subroutine

! ------------------------------------------------------------------------------
    function poly_init_1(order) result(rst)
        !! Initializes a new [[polynomial]] instance.
        integer(int32), intent(in) :: order
            !! The order of the polynomial (must be >= 0).
        type(polynomial) :: rst
            !! The new [[polynomial]] object.

        call rst%initialize(order)
    end function

! ------------------------------------------------------------------------------
    function poly_init_2(c) result(rst)
        !! Initializes a new [[polynomial]] instance.
        real(real64), intent(in), dimension(:) :: c
            !! The array of polynomial coefficients. The coefficients are
            !! established as follows: c(1) + c(2) * x + c(3) * x**2 + ...
            !! c(n) * x**n-1.
        type(polynomial) :: rst
            !! The new [[polynomial]] object.

        call rst%initialize(c)
    end function

! ------------------------------------------------------------------------------
end module